<?php
namespace GoodText\App;

class Router
{
    private $arrayRoutes     = array(); // 存储路由
    private $baseRoute       = ''; // 基础路径
    private $requestedMethod = ''; // 需要处理的请求方法
    private $serverBasePath; // 路由器执行的服务器基本路径
    private $controllerPath = ''; // @var string 控制器路径
    private $namespace       = ''; // namespace

    // addRoute存储路由与访问时要执行的路由和处理函数 （$methods - HTTP 请求方式 用|隔开 GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD ，$pattern - 静态路由或基于PCRE的正则路由 ，$fn - 执行的处理函数）
    public function addRoute($methods, $pattern, $fn)
    {
        $pattern = $this->baseRoute . '/' . trim($pattern, '/');
        $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;
        foreach (explode('|', $methods) as $method) {
            $this->arrayRoutes[$method][] = array(
                'pattern' => $pattern,
                'fn'      => $fn,
            );
        }
    }
    // 获取请求的类型
    public function getRequestMethod()
    {
        $method = $_SERVER['REQUEST_METHOD'];
        if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
            ob_start();
            $method = 'GET';
        } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
            // 获取HTTP头信息
            $headers = array();
            // 检测getallheaders()是否可用
            if (function_exists('getallheaders')) {
                $headers = getallheaders();
            }
            if (!$headers == false) {
                $headers = $headers;
            } else {
                // getallheaders（）不可用或出错则提取
                foreach ($_SERVER as $name => $value) {
                    if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) {
                        $headers[str_replace(array(' ', 'Http'), array('-', 'HTTP'), ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
                    }
                }
            }
            if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) {
                $method = $headers['X-HTTP-Method-Override'];
            }
        }
        return $method;
    }
    // 设置命名空间
    public function setNamespace($namespace)
    {
        if (is_string($namespace)) {
            $this->namespace = $namespace;
        }
    }
    // 获取命名空间
    public function getNamespace()
    {
        return $this->namespace;
    }
    // 执行，可调用$callback处理匹配路由后执行的函数
    public function dispatch($callback = null)
    {
        $this->requestedMethod = $this->getRequestMethod(); // 定义需要处理的方法
        $numHandle             = 0; // 计数处理过的路线数量
        if (isset($this->arrayRoutes[$this->requestedMethod])) {
            $numHandle = $this->handle($this->arrayRoutes[$this->requestedMethod]);
        }
        // 如果未处理路由，则返回404
        if ($numHandle === 0) {
            header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
            echo '404, route not found!';
        } elseif ($numHandle < 0) {
            switch ($numHandle) {
                case -1:
                    $error = ' 加载文件不存在!';
                    break;
                case -2:
                    $error = ' 没有找到类!';
                    break;
                case -3:
                    $error = ' 调用的方法不存在!';
                    break;
                default:
                    $error = ' 未知错误!';
                    break;
            }
            header($_SERVER['SERVER_PROTOCOL'] . $error);
            echo $error;
        } else {
            // 如果处理了路由，则执行回调
            if ($callback && is_callable($callback)) {
                //判断是否存在回调函数
                $callback();
            }
        }
        // 如果是HEAD请求，则清空输出缓冲区
        if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
            ob_end_clean(); // 清空输出缓冲区
        }
        return $numHandle !== 0; // 处理了路由返回true，否则返回false
    }
    // 处理匹配路由（循环路由定义的所有内容，如匹配，则执行处理函数）。
    private function handle($routes)
    {
        $numHandle = 0; // 计数处理过的路线数量
        // 当前URL
        $uri = $this->getCurrentUri();
        // 循环所有路由
        foreach ($routes as $route) {
            // 匹配
            if (boolval(preg_match_all('#^' . $route['pattern'] . '$#', $uri, $matches, PREG_OFFSET_CAPTURE))) {
                $matches = array_slice($matches, 1); // 返回数组中用到的参数部分，不包含原始字符串
                $params  = array(); // 参数
                //按参数数量，将其添加到数组中
                switch (count($matches)) {
                    case 1:
                        $params = array(trim($matches[0][0][0],'/'));
                        break;
                    case 2:
                        $params = array(explode('/', trim($matches[0][0][0],'/'),2)[0], trim($matches[1][0][0],'/'));
                        break;
                    case 3:
                        $params = array(explode('/', trim($matches[0][0][0],'/'),2)[0], explode('/', trim($matches[1][0][0],'/'),2)[0], trim($matches[2][0][0],'/'));
                        break;
                    case 4:
                        $params = array(explode('/', trim($matches[0][0][0],'/'),2)[0], explode('/', trim($matches[1][0][0],'/'),2)[0], explode('/', trim($matches[2][0][0],'/'),2)[0],  trim($matches[3][0][0],'/'));
                        break;
                    case 5:
                        $params = array(explode('/', trim($matches[0][0][0],'/'),2)[0], explode('/', trim($matches[1][0][0],'/'),2)[0], explode('/', trim($matches[2][0][0],'/'),2)[0], explode('/', trim($matches[3][0][0],'/'),2)[0], trim($matches[4][0][0],'/'));
                        break;
                    case 6:
                        $params = array(explode('/', trim($matches[0][0][0],'/'),2)[0], explode('/', trim($matches[1][0][0],'/'),2)[0], explode('/', trim($matches[2][0][0],'/'),2)[0], explode('/', trim($matches[3][0][0],'/'),2)[0], explode('/', trim($matches[4][0][0],'/'),2)[0], trim($matches[5][0][0],'/'));
                        break;
                    default:
                        break;
                }
                if (is_callable($route['fn'])) { // 是否一个有效的函数
                    call_user_func_array($route['fn'], $params); // 数组$params作为参数传递给该函数
                } elseif (stripos($route['fn'], '@') !== false) {
                    // 请检查是否存在特殊参数“@”
                    list($scontroller, $smethod) = explode('@', $route['fn']); // 分割字符串
                    $controller                  = 'My_' . $scontroller . 'Controller'; // 规范控制器名称
                    $method                      = 'my_' . $smethod . 'Action'; // 规范方法名称
                    if ($this->getNamespace() !== '') {
                        $controller = $this->getNamespace() . '\\' . $controller; // 如果已设置命名空间，则调整控制器类
                    } else {
                        $controller = '\\' . $controller;
                    }
                    if ($this->getControllerPath() !== '') {
                        $fileName = '.' . $this->getControllerPath() . str_replace('\\', '/', $controller) . '.php'; // 调整加载文件路径
                    } else {
                        $fileName = '.' . str_replace('\\', '/', $controller) . '.php'; // 调整加载文件路径
                    }
                    if (is_file($fileName)) { // 判断文件是否存在
                        require $fileName; // 加载文件，可以使用自动加载，自动加载时注销这几行。
                    } else {
                        return -1; // 文件不存在
                    }
                    if (class_exists($controller, false)) {
                        // 检查类是否可用
                        $controller_object = new $controller;
                    } else {
                        return -2; // 没有找到类
                    }
                    if (method_exists($controller_object, $method)) {
                        // 检查类的方法是否存在
                        // 调用方法，有参数时传入参数
                        if ($params) {
                            $controller_object->$method(implode(",", $params));
                        } else {
                            $controller_object->$method();
                        }
                    } else {
                        return -3; // 调用的方法不存在
                    }
                    //使用call_user_func_array：return method_exists($controller, $method) && is_callable([$controller, $method]) ? call_user_func_array([$controller,$method], $params) : false;
                }
                ++$numHandle;
                break; // 成功匹配并处理后终止循环
            }
        }
        return $numHandle; // 返回已处理的路由数
    }
    // 获取当前相对uri
    public function getCurrentUri()
    {
        $uri = substr(rawurldecode($_SERVER['REQUEST_URI']), strlen($this->getBasePath())); // 获取当前的请求URI并从中删除基础路径，实现在子文件夹中运行
        if (strstr($uri, '?')) {
            $uri = substr($uri, 0, strpos($uri, '?')); // 截取，去掉参数
        }
        return '/' . trim($uri, '/'); // 用“/”开始，删除末尾“/”
    }
    // 设置基础路径
    public function setBasePath($serverBasePath)
    {
        $this->serverBasePath = $serverBasePath;
    }
    // 获取基本路径，可以将代码放在Web服务器的根目录或子文件夹中
    public function getBasePath()
    {
        // 检查是否设置了服务器基本路径，如果没有，则从$_SERVER['SCRIPT_NAME']中获取。
        if ($this->serverBasePath === null) {
            $this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/'; // 拼接路径， array_slice() 返回数组中的选定部分
        }
        return $this->serverBasePath;
    }
    // 设置控制器路径
    public function setControllerPath($controllerpath)
    {
        $this->controllerPath = $controllerpath;
    }
    // 获取控制器路径
    public function getControllerPath()
    {
        return $this->controllerPath;
    }
}